-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 스토리 등록 구현 #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat] 스토리 등록 구현 #80
Conversation
|
""" Walkthrough스토리(Story) 기능이 도메인, 서비스, 컨트롤러, DTO, 예외, 리포지토리, 테스트, 마이그레이션 등 전반에 걸쳐 새롭게 도입되었습니다. 사용자는 매장 검색 결과에서 매장을 선택해 스토리를 등록할 수 있으며, 이미지 업로드 및 다양한 유효성 검증이 적용됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant StoryController
participant StoryService
participant StoreService
participant ImageService
participant StoryRepository
participant MemberRepository
Client->>StoryController: POST /api/stories (multipart/form-data)
StoryController->>StoryService: registerStory(request, image, memberId)
StoryService->>StoreService: searchStoreResults(query)
StoreService-->>StoryService: List<StoreSearchResult>
StoryService->>ImageService: upload(image, ImageDomain.STORY)
ImageService-->>StoryService: imageKey
StoryService->>MemberRepository: findById(memberId)
MemberRepository-->>StoryService: Member
StoryService->>StoryRepository: save(Story)
StoryRepository-->>StoryService: Story
StoryService-->>StoryController: (void)
StoryController-->>Client: 201 Created
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
📜 Recent review detailsConfiguration used: .coderabbit.yaml 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Nitpick comments (8)
src/main/java/eatda/service/store/StoreService.java (1)
23-26: 코드 중복 리팩토링 제안새로운 메서드
searchStoreResults가 기존searchStores메서드와 유사한 로직을 가지고 있습니다. 공통 로직을 추출하여 중복을 제거할 수 있습니다.다음과 같이 리팩토링할 수 있습니다:
public StoreSearchResponses searchStores(String query) { - List<StoreSearchResult> searchResults = mapClient.searchShops(query); - List<StoreSearchResult> filteredResults = storeSearchFilter.filterSearchedStores(searchResults); - return StoreSearchResponses.from(filteredResults); + return StoreSearchResponses.from(searchStoreResults(query)); } public List<StoreSearchResult> searchStoreResults(String query) { List<StoreSearchResult> searchResults = mapClient.searchShops(query); return storeSearchFilter.filterSearchedStores(searchResults); }src/test/java/eatda/controller/story/StoryControllerTest.java (1)
29-30: 중첩 클래스명 수정 필요중첩 클래스명이
SearchStores로 되어 있지만, 실제로는 스토리 등록을 테스트하고 있습니다. 클래스명을RegisterStory로 변경하는 것이 적절합니다.@Nested - class SearchStores { + class RegisterStory {src/main/java/eatda/service/story/StoryService.java (2)
50-61: 매장 검색 성능 최적화를 고려해보세요.현재 구현은 모든 검색 결과를 가져온 후 클라이언트에서 필터링하고 있습니다. 검색 결과가 많을 경우 성능상 비효율적일 수 있습니다.
다음과 같은 개선을 제안합니다:
StoreService에서kakaoId로 직접 매장을 조회하는 메서드 추가- 또는 검색 단계에서 특정 매장 ID로 필터링하는 기능 추가
// 제안: StoreService에 새로운 메서드 추가 public Optional<FilteredSearchResult> findStoreByKakaoId(String query, String kakaoId) { // 검색 결과에서 직접 특정 매장만 반환 }
50-50: 메서드명이 의도를 명확히 나타내지 않습니다.
filteredSearchResponse메서드명이 단순히 "필터링된 응답"을 의미하여 구체적인 기능을 파악하기 어렵습니다.다음과 같은 명명을 제안합니다:
private FilteredSearchResult findStoreByKakaoId(List<StoreSearchResult> responses, String storeKakaoId)src/main/java/eatda/controller/story/StoryController.java (1)
19-19: 기본 경로 매핑 고려해보세요.컨트롤러 클래스 레벨에서
@RequestMapping("/api/stories")를 사용하여 경로를 정리하고, 메서드에서는@PostMapping만 사용하는 것을 고려해보세요.@RestController @RequestMapping("/api/stories") public class StoryController { @PostMapping public ResponseEntity<Void> registerStory(...) }src/main/resources/db/migration/V3__add_story_table.sql (1)
1-13: 데이터베이스 스키마 설계가 적절합니다.테이블 구조와 제약 조건들이 잘 정의되어 있습니다. 다만 몇 가지 고려사항이 있습니다:
member_id에 대한 외래 키 제약 조건이 명시되지 않았습니다- 조회 성능을 위한 인덱스 추가를 고려해보세요 (예:
member_id,created_at)- 매장 정보가 비정규화되어 있는데, 이는 성능상 합리적인 선택으로 보입니다
다음과 같은 개선사항을 고려해보세요:
-- 외래 키 제약 조건 추가 ALTER TABLE `story` ADD CONSTRAINT `fk_story_member` FOREIGN KEY (`member_id`) REFERENCES `member` (`id`); -- 성능을 위한 인덱스 추가 CREATE INDEX `idx_story_member_id` ON `story` (`member_id`); CREATE INDEX `idx_story_created_at` ON `story` (`created_at`);src/main/java/eatda/exception/BusinessErrorCode.java (1)
45-54: 스토리 관련 오류 코드 메시지의 일관성을 개선하세요.스토리 관련 오류 코드들이 잘 정의되었지만, 메시지에서 조사 사용이 일관적이지 않습니다.
- INVALID_STORY_DESCRIPTION("STY001", "스토리 본문은 필수입니다."), - INVALID_STORY_IMAGE_URL("STY002", "스토리 이미지 URL은 필수입니다."), - STORY_MEMBER_REQUIRED("STY003", "스토리 작성 시 회원 정보는 필수입니다."), - STORY_STORE_REQUIRED("STY004", "스토리 작성 시 가게 정보는 필수입니다."), - INVALID_STORE_KAKAO_ID("STY007", "스토어 Kakao ID는 필수입니다."), - INVALID_STORE_NAME("STY008", "스토어 이름은 필수입니다."), - INVALID_STORE_ADDRESS("STY009", "스토어 주소는 필수입니다."); + INVALID_STORY_DESCRIPTION("STY001", "스토리 본문은 필수입니다."), + INVALID_STORY_IMAGE_URL("STY002", "스토리 이미지 URL은 필수입니다."), + STORY_MEMBER_REQUIRED("STY003", "스토리 작성 시 회원 정보는 필수입니다."), + STORY_STORE_REQUIRED("STY004", "스토리 작성 시 가게 정보는 필수입니다."), + INVALID_STORE_KAKAO_ID("STY007", "스토어 Kakao ID는 필수입니다."), + INVALID_STORE_NAME("STY008", "스토어 이름은 필수입니다."), + INVALID_STORE_ADDRESS("STY009", "스토어 주소는 필수입니다.");모든 메시지에서 "~는 필수입니다" 형태로 통일하는 것을 권장합니다.
src/test/java/eatda/document/story/StoryDocumentTest.java (1)
102-134: 이미지 형식 오류 테스트를 개선하세요.테스트 로직은 적절하지만, 다음 사항들을 개선할 수 있습니다:
- 디버깅용 System.out.println 제거
- 더 명확한 테스트 데이터 사용
- System.out.println("응답 상태코드 >>> " + response.statusCode()); - System.out.println("응답 바디 >>> " + response.asString());디버깅이 필요한 경우 로깅 프레임워크를 사용하거나, 테스트 완료 후 제거하는 것이 좋습니다.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (23)
src/main/java/eatda/controller/story/FilteredSearchResult.java(1 hunks)src/main/java/eatda/controller/story/StoriesResponse.java(1 hunks)src/main/java/eatda/controller/story/StoryController.java(1 hunks)src/main/java/eatda/controller/story/StoryRegisterRequest.java(1 hunks)src/main/java/eatda/controller/story/StoryResponse.java(1 hunks)src/main/java/eatda/domain/story/Story.java(1 hunks)src/main/java/eatda/exception/BusinessErrorCode.java(2 hunks)src/main/java/eatda/exception/GlobalExceptionHandler.java(3 hunks)src/main/java/eatda/repository/story/StoryRepository.java(1 hunks)src/main/java/eatda/service/common/ImageDomain.java(1 hunks)src/main/java/eatda/service/common/ImageService.java(3 hunks)src/main/java/eatda/service/store/StoreService.java(1 hunks)src/main/java/eatda/service/story/StoryService.java(1 hunks)src/main/resources/db/migration/V3__add_story_table.sql(1 hunks)src/test/java/eatda/controller/BaseControllerTest.java(2 hunks)src/test/java/eatda/controller/story/StoryControllerTest.java(1 hunks)src/test/java/eatda/document/BaseDocumentTest.java(3 hunks)src/test/java/eatda/document/Tag.java(1 hunks)src/test/java/eatda/document/story/StoryDocumentTest.java(1 hunks)src/test/java/eatda/domain/story/StoryTest.java(1 hunks)src/test/java/eatda/service/BaseServiceTest.java(2 hunks)src/test/java/eatda/service/common/ImageServiceTest.java(3 hunks)src/test/java/eatda/service/story/StoryServiceTest.java(1 hunks)
🧰 Additional context used
🧠 Learnings (5)
src/test/java/eatda/controller/BaseControllerTest.java (1)
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#60
File: src/test/java/eatda/controller/store/StoreControllerTest.java:10-32
Timestamp: 2025-07-09T07:56:50.612Z
Learning: 컨트롤러 테스트에서 MockitoBean으로 의존성을 모킹한 경우, 상세한 비즈니스 로직 검증보다는 컨트롤러 계층의 동작(라우팅, 파라미터 처리, 응답 구조 등)을 검증하는 것이 더 적절합니다. 모킹된 데이터에 대한 상세 검증은 의미가 없기 때문입니다.
src/main/java/eatda/service/common/ImageService.java (1)
Learnt from: lvalentine6
PR: YAPP-Github/26th-Web-Team-1-BE#68
File: src/main/java/eatda/service/common/ImageService.java:24-24
Timestamp: 2025-07-09T20:11:28.800Z
Learning: ImageService에서 MIME 타입 검증 시 "image/jpg"와 "image/jpeg" 모두 허용해야 함. "image/jpg"는 비표준이지만 실제 환경에서 여전히 사용되어 호환성 문제 방지를 위해 두 타입 모두 지원하는 것이 실용적임.
src/test/java/eatda/controller/story/StoryControllerTest.java (1)
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#60
File: src/test/java/eatda/controller/store/StoreControllerTest.java:10-32
Timestamp: 2025-07-09T07:56:50.612Z
Learning: 컨트롤러 테스트에서 MockitoBean으로 의존성을 모킹한 경우, 상세한 비즈니스 로직 검증보다는 컨트롤러 계층의 동작(라우팅, 파라미터 처리, 응답 구조 등)을 검증하는 것이 더 적절합니다. 모킹된 데이터에 대한 상세 검증은 의미가 없기 때문입니다.
src/test/java/eatda/document/BaseDocumentTest.java (1)
Learnt from: leegwichan
PR: YAPP-Github/26th-Web-Team-1-BE#60
File: src/main/java/eatda/controller/store/StoreController.java:18-21
Timestamp: 2025-07-09T08:05:53.497Z
Learning: Spring Boot 컨트롤러에서 LoginMember 등의 인증 파라미터는 메서드 내에서 직접 사용되지 않더라도 접근 제어(인증된 사용자만 접근 가능)를 위해 필요할 수 있다. 이는 보안상 유효한 패턴이다.
src/test/java/eatda/service/common/ImageServiceTest.java (1)
Learnt from: lvalentine6
PR: YAPP-Github/26th-Web-Team-1-BE#68
File: src/main/java/eatda/service/common/ImageService.java:24-24
Timestamp: 2025-07-09T20:11:28.800Z
Learning: ImageService에서 MIME 타입 검증 시 "image/jpg"와 "image/jpeg" 모두 허용해야 함. "image/jpg"는 비표준이지만 실제 환경에서 여전히 사용되어 호환성 문제 방지를 위해 두 타입 모두 지원하는 것이 실용적임.
🧬 Code Graph Analysis (2)
src/test/java/eatda/document/story/StoryDocumentTest.java (2)
src/test/java/eatda/document/RestDocsRequest.java (1)
RestDocsRequest(18-75)src/test/java/eatda/document/RestDocsResponse.java (1)
RestDocsResponse(14-40)
src/main/java/eatda/controller/story/StoryController.java (1)
src/main/java/eatda/controller/web/auth/AuthMemberArgumentResolver.java (1)
RequiredArgsConstructor(14-36)
🔇 Additional comments (35)
src/main/java/eatda/service/common/ImageDomain.java (1)
1-15: 잘 구현된 이미지 도메인 enum입니다.타입 안전성을 제공하고 Lombok 어노테이션을 적절히 활용한 깔끔한 구현입니다. 새로운 STORY 도메인이 추가되어 스토리 기능을 잘 지원하고 있습니다.
src/main/java/eatda/controller/story/FilteredSearchResult.java (1)
3-9: 매장 검색 결과를 위한 적절한 record 구조입니다.필드명이 명확하고 매장 정보를 담기에 적절한 구조로 설계되었습니다. 응답 DTO로서의 역할을 잘 수행할 것으로 보입니다.
src/test/java/eatda/document/Tag.java (1)
7-8: 스토리 API 문서화를 위한 적절한 태그 추가입니다.새로운 STORY_API 태그가 기존 패턴에 맞게 잘 추가되었으며, API 문서화 분류에 도움이 될 것입니다.
src/test/java/eatda/service/BaseServiceTest.java (2)
8-10: 스토리 기능 테스트를 위한 적절한 import 추가입니다.새로운 스토리 기능 테스트에 필요한 의존성들이 올바르게 import되었습니다.
32-39: 스토리 관련 테스트를 위한 Mock 빈 설정이 잘 구성되었습니다.StoreService, ImageService, StoryRepository의 MockitoBean 설정이 적절하게 추가되어 스토리 기능 테스트를 위한 기반을 제공합니다.
src/test/java/eatda/controller/BaseControllerTest.java (1)
59-63: 테스트 인프라 확장 승인새로운 스토리 기능을 위한
StoryService와ImageService모킹이 적절하게 추가되었습니다. 기존 패턴과 일치하며, 컨트롤러 테스트에서 필요한 의존성을 효과적으로 모킹할 수 있습니다.src/main/java/eatda/controller/story/StoriesResponse.java (1)
5-13: 깔끔한 DTO 레코드 구현
StoriesResponse와 중첩된StoryPreview레코드가 잘 설계되었습니다. 불변성을 보장하고 명확한 네이밍으로 스토리 미리보기 데이터를 효과적으로 전달할 수 있습니다.src/main/java/eatda/exception/GlobalExceptionHandler.java (3)
5-5: 로깅 기능 추가 승인
@Slf4j어노테이션 추가로 예외 처리에 로깅 기능이 도입되었습니다.
82-82: 적절한 비즈니스 예외 로깅
BusinessException처리 시 에러 코드를 로깅하는 것이 디버깅과 모니터링에 도움이 됩니다.
90-90: 포괄적인 예외 로깅일반 예외 처리 시 예외 클래스명, 메시지, 스택 트레이스를 모두 로깅하여 문제 진단에 충분한 정보를 제공합니다.
src/test/java/eatda/controller/story/StoryControllerTest.java (2)
18-27: 적절한 컨트롤러 테스트 모킹
imageService와storyService의 모킹이 컨트롤러 레이어 테스트에 적합하게 설정되었습니다. 고정된 응답값과 동작을 제공하여 컨트롤러의 라우팅과 요청 처리에 집중할 수 있습니다.
32-54: 멀티파트 요청 테스트 잘 구현됨스토리 등록 API의 멀티파트 요청 처리가 적절하게 테스트되었습니다. JSON 요청 데이터와 이미지 파일을 함께 전송하고 201 상태 코드로 성공을 확인하는 것이 컨트롤러 레이어 테스트에 적합합니다.
src/main/java/eatda/controller/story/StoryResponse.java (1)
3-11: 깔끔한 DTO 구현입니다.Java record를 사용한 불변 데이터 전송 객체로 적절히 구현되었습니다. 필드명도 명확하고 스토리 응답에 필요한 정보를 잘 담고 있습니다.
src/main/java/eatda/service/common/ImageService.java (4)
23-23: 상수 추출로 가독성이 향상되었습니다.하드코딩된 "bin" 문자열을
DEFAULT_CONTENT_TYPE상수로 추출하여 유지보수성이 개선되었습니다.
41-41: 타입 안전성이 향상되었습니다.
String매개변수를ImageDomain열거형으로 변경하여 컴파일 타임에 유효한 도메인만 전달할 수 있도록 개선되었습니다.
45-45: 도메인 기반 키 생성이 적절합니다.
domain.getName()을 사용하여 S3 키를 생성하는 것이 기존 문자열 방식보다 안전하고 일관성 있습니다.
70-70: 일관성 있는 상수 사용.
DEFAULT_CONTENT_TYPE상수를 사용하여 코드 일관성이 향상되었습니다.src/main/java/eatda/service/story/StoryService.java (1)
30-48: 트랜잭션 범위와 전반적인 플로우가 적절합니다.스토리 등록 로직이 올바른 순서로 구현되어 있습니다: 사용자 검증 → 매장 검증 → 이미지 업로드 → 엔티티 생성 → 저장.
@Transactional애노테이션도 적절히 적용되었습니다.src/main/java/eatda/controller/story/StoryController.java (1)
13-28: REST 컨트롤러 구조가 적절합니다.멀티파트 데이터 처리와 인증된 사용자 정보 사용이 올바르게 구현되었습니다. HTTP 201 Created 상태 코드도 리소스 생성에 적합합니다.
src/test/java/eatda/service/common/ImageServiceTest.java (3)
20-21: 파라미터화된 테스트 도입이 우수합니다.
@ParameterizedTest와@EnumSource를 활용하여 모든 ImageDomain에 대한 테스트 커버리지를 개선했습니다.
54-78: ImageDomain 열거형 활용으로 테스트 코드가 개선되었습니다.하드코딩된 문자열 대신
ImageDomain열거형을 사용하여 타입 안전성과 유지보수성이 향상되었습니다. 파라미터화된 테스트로 모든 도메인에 대한 일관된 테스트가 가능해졌습니다.
86-90: ImageDomain.STORY 상수 사용이 적절합니다.하드코딩된 문자열 대신 열거형 상수를 사용하여 코드의 일관성이 향상되었습니다.
src/test/java/eatda/domain/story/StoryTest.java (1)
1-160: 도메인 검증 테스트가 포괄적이고 잘 구성되어 있습니다.스토리 도메인의 모든 검증 시나리오를 체계적으로 테스트하고 있습니다:
- 중첩 클래스로 기능별 테스트 그룹화
- 성공 케이스와 실패 케이스 모두 포함
- 모든 필수 필드에 대한 검증 테스트
- 적절한 에러 메시지 검증
- Mockito를 활용한 의존성 모킹
도메인 불변성이 올바르게 강제되고 있음을 보장하는 우수한 테스트 코드입니다.
src/test/java/eatda/document/BaseDocumentTest.java (3)
9-15: 새로운 서비스 의존성 추가가 적절합니다.스토리 기능을 위한
StoryService와ImageService의존성이 올바르게 추가되었습니다. 관련 import 문도 적절히 포함되었습니다.
51-56: MockitoBean 설정이 올바르게 구성되었습니다.새로운 서비스들에 대한 모킹 설정이 기존 패턴과 일관성 있게 추가되었습니다.
104-106: EtcErrorCode 지원 메소드 추가가 일관성 있습니다.기존
BusinessErrorCode와 동일한 패턴으로EtcErrorCode를 지원하는 메소드가 추가되어 API 문서화의 일관성이 향상되었습니다.src/test/java/eatda/service/story/StoryServiceTest.java (2)
54-67: 실패 시나리오 테스트가 적절합니다.매장을 찾을 수 없는 경우에 대한 예외 처리가 올바르게 테스트되고 있습니다. 에러 메시지 검증도 적절히 수행되고 있습니다.
21-31: 테스트 구조와 설정이 잘 구성되어 있습니다.
BaseServiceTest를 상속받아 공통 설정을 재사용하고, 의존성 주입을 통한 서비스 초기화가 적절히 구현되었습니다.src/main/java/eatda/exception/BusinessErrorCode.java (1)
24-24: 새로운 스토어 오류 코드 추가가 적절합니다.STORE_NOT_FOUND 오류 코드가 기존 스토어 오류 코드 패턴과 일치하며, 메시지도 명확합니다.
src/main/java/eatda/domain/story/Story.java (4)
21-25: 엔티티 구조가 잘 설계되었습니다.JPA 엔티티 어노테이션과 Lombok 어노테이션이 적절히 사용되었으며, AuditingEntity 상속으로 생성/수정 시간 관리가 자동화되었습니다.
31-33: Member와의 연관관계 설정이 적절합니다.지연 로딩 설정으로 성능이 최적화되었고, nullable = false로 필수 관계가 명확히 정의되었습니다.
53-74: 빌더 패턴과 유효성 검사 로직이 우수합니다.생성자에서 즉시 유효성 검사를 수행하고, 검사 로직이 논리적으로 그룹화되어 있어 가독성이 좋습니다.
76-128: 유효성 검사 메서드들이 잘 분리되어 있습니다.각 필드별로 private 메서드로 분리된 유효성 검사 로직이 가독성과 유지보수성을 향상시킵니다. BusinessException 사용으로 일관된 예외 처리가 구현되었습니다.
src/test/java/eatda/document/story/StoryDocumentTest.java (2)
29-37: REST Docs 요청 문서화 설정이 적절합니다.Tag.STORY_API를 사용한 태그 분류와 명확한 summary/description 설정이 잘 되어 있습니다.
39-71: 성공 테스트 케이스가 잘 구현되었습니다.적절한 모킹 설정과 multipart 요청 구성, 응답 상태 코드 검증이 포함되어 있습니다.
📄 Terraform Plan Summary🛡️ Common InfrastructureStatus: ✅ No Changes 🛠️ Development EnvironmentStatus: ✅ No Changes 📋 Full Results: View in Actions |
|
📌 최신 ERD가 자동 생성되었습니다. 👉 ERD 보러가기 |
leegwichan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/noti 승로님 고생 많으셨어요! 처음으로 Service, Controller, Document 테스트를 작성하기도 하니 저와 다른 컨텍스트로 진행되는 부분이 있는 것 같아요. 관련해서 리뷰 남겼습니다.
일단 개발 작업이 우선이니 넘어가셔도 문제 없을 것 같습니다. 빠르게 작업 마무리하고 2차 스프린트 들어가기 전에 리팩토링 한 번 하는걸로 하시죠!
| @PostMapping("/api/stories") | ||
| public ResponseEntity<Void> registerStory( | ||
| @RequestPart("request") StoryRegisterRequest request, | ||
| @RequestPart("image") MultipartFile image, | ||
| LoginMember member | ||
| ) { | ||
| storyService.registerStory(request, image, member.id()); | ||
| return ResponseEntity.status(HttpStatus.CREATED).build(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[제안] POST 요청에서 StoryResponse를 응답하는 건 어떨까요?
- POST 요청에서 어떻게 만들어졌는지 Response를 반환하는 게 일반적이라고 생각했어요.
- 클라이언트가 해당 값을 사용하지 않을 거라면, (실용적인 입장에서) 추가하지 않아도 된다고 생각합니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오 생각해보니 스토리 등록후에 어디로 리다이렉트 될지 명확하지 않은 상황이네요.
저는 스토리 생성 후 스토리 목록으로 돌아간다고 생각해서 바디를 넣지는 않았는데
이 부분은 프론트팀에 물어보고 필요하다면 수정할께요 :)
| private void validateStore(String storeKakaoId, String storeName, String storeAddress, String storeCategory) { | ||
| validateStoreKakaoId(storeKakaoId); | ||
| validateStoreName(storeName); | ||
| validateStoreAddress(storeAddress); | ||
| validateStoreCategory(storeCategory); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[검토] 외부 API를 통해 받는 값들은 검증을 하면 안된다고 생각하는데, Kakao API 문서를 참고하셨다면 크게 문제는 없다고 생각합니다. 혹시 모르니 한 번 더 확인 부탁드릴께요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오.. Kakao API 문서를 참고해서 작성하고 연동 테스트를 거쳤습니다!
외부 API에서 온 값들은 검증을 하지 않아야 한다는 이유가 무엇일까요?
저는 외부에서 들어오는 값이기 때문에 더 검증이 필요하다고 생각했는데요
외부 API 응답에 문제가 있거나 구조가 변경되는 상황을 고려하면,
도메인 로직이 그 영향에서 최대한 독립적이어야 하지 않을까 해서요.
만약 검증을 하지 않는다면
저희 서비스가 외부 API에 완전 종속적이고 장애에 그대로 영향을 받기에 최소한의 방어를 하고 싶었어요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
외부에서 들어오는 값이기 때문에 더 검증이 필요하다고 생각했는데요
저도 외부에서 들어온 값을 검증해야 한다는 것에는 동의합니다. 그런데 "Kakao API 에서 들어온 값이 우리가 정한 도메인 규칙과 일치하지 않아서 가게가 등록되지 않는 상황"에 대한 염려가 큰 것 같아요. 외부 API에서 들어온 모든 정보가 일정한 형식이라는 보장이 되기는 힘드니까요;; 적어도 "특정 형식이 아닐 경우 설정할 기본값"이 있어야 할 것 같아요.
지금은 크게 문제 없을 것 같아 넘어가시죠! 코드 형식적으로 논의해볼 내용들은 제가 한 번 다 정리해서 이야기해보면 좋을 것 같아요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오... 그런 부분도 있겠네요
DTO 계층에서의 유연하게 방어할수 있는 기본값 설정을 하면 딱 좋을것 같네요 😄
2차 스프린트 이후에 리펙토링 시기에 고민해보시죠!
| @Column(name = "store_category", nullable = false) | ||
| private String storeCategory; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
category 는 StoreCategory 값이 들어가야 할 것 같은데요. 해당 부분은 Kakao API의 응답값에 따라 Enum으로 바꿔주는 작업이 필요합니다.
일단 넘어가신다면 제가 응원 등록 API 만들면서 같이 작업하도록 하겠습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아! 이전에 만들어둔 StoreCategory enum이 있었네요
일단은 Kakao API 응답값 그대로 사용하는 쪽으로 진행했는데,
응원 등록 쪽 작업하시면서 해당 enum으로 매핑하신다면 좋을 것 같습니다.
필요하시면 이후 작업에도 같이 맞춰갈게요!
| @Test | ||
| void 스토리를_등록할_수_있다() { | ||
| String requestJson = """ | ||
| { | ||
| "query": "농민백암순대", | ||
| "storeKakaoId": "123", | ||
| "description": "여기 진짜 맛있어요!" | ||
| } | ||
| """; | ||
|
|
||
| byte[] imageBytes = "dummy image content".getBytes(StandardCharsets.UTF_8); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
테스트 작성하시느라 고생 많으셨어요!
이 부분에 대해서는 제가 추후에 개선안을 찾아볼께요!
|
|
||
| @MockitoBean | ||
| protected StoreService storeService; | ||
|
|
||
| @MockitoBean | ||
| protected ImageService imageService; | ||
|
|
||
| @MockitoBean | ||
| protected StoryRepository storyRepository; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[수정 필요] ServiceTest 같은 경우에는 H2 DB를 이용하여 테스트를 진행하는데요. 수정이 필요할 것 같습니다.
ImageService만 Mock으로 이용하고 나머지는 실제 객체로 테스트 해주시면 좋을 것 같아요!- ServiceTest 관련해서 궁금한 부분 있으시면 편하게 말씀 주세요! (바쁘시다면 문서로 정리해서 남겨놓도록 할께요!)
- 다른 작업이 있어 바쁘시다면 넘어가셔도 좋습니다. 제가 개선하도록 할께요~!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 StoryRepository 부분을 제가 Mock으로 사용했군요
이 부분은 바로 수정할께요!
그런데 StoreService는 Kakao API로 요청이 발생하게 되는데
이전에 StoreServiceTest, StoreSearchFilterTest에서도 Mockito.doReturn 으로 테스트 하셨는데
어디서 실제 객체로 사용해야 한다고 생각하시는건지 궁금합니다.
말씀해주시면 제가 수정하겠습니다~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
서비스 테스트에서는 기본적으로 **"되도록 모든 객체를 실제 객체를 사용하고, DB만 H2로 사용한다"**는 생각을 하고 있어요. 그런데 테스트를 할 때마다 외부 API에 요청이 가면 안되니까 **"외부 API를 사용하는 최소한의 단위 객체만 Mock을 사용"**하도록 노력하고 있습니다.
현재 작업 환경에서는 외부 API를 호출하는 부분이 MapClient, OauthClient, ImageService 이어서 해당 부분만 Mock으로 처리하고, 나머지 부분은 실 객체를 사용하는 것을 생각하고 있습니다.
그런데 StoreService는 Kakao API로 요청이 발생하게 되는데
이전에 StoreServiceTest, StoreSearchFilterTest에서도 Mockito.doReturn 으로 테스트 하셨는데
어디서 실제 객체로 사용해야 한다고 생각하시는건지 궁금합니다.
이건 제가 잘못한 것 같네요;; 추후에 아래와 같이 수정하겠습니다!
- ServiceTest 환경에서는 이미
MapClient는 Mock 객체가 빈으로 등록되어 있다. - StoreService를 테스트 할 때는 ServiceTest 환경에서 등록된 빈으로 테스트를 진행한다.
@Autowired
private StoreService storeService; // Spring Container에 등록된 객체를 사용하여 테스트
@BeforeEach
void mockingClient() {
List<StoreSearchResult> searchResults = ...;
doReturn(searchResults).when(mapClient).searchShops(anyString()); // BaseServiceTest 에 mapClient를 Mock 객체로 등록함
}
@Nested
class SearchStores {
@Test
void 음식점_검색_결과를_반환한다() {
String query = "농민백암순대";
var response = storeService.searchStores(query);
assertAll(
() -> assertThat(response.stores()).hasSize(2),
() -> assertThat(response.stores().get(0).kakaoId()).isEqualTo("123"),
() -> assertThat(response.stores().get(0).address()).isEqualTo("서울 강남구 대치동 896-33"),
() -> assertThat(response.stores().get(1).kakaoId()).isEqualTo("456"),
() -> assertThat(response.stores().get(1).address()).isEqualTo("서울 중구 북창동 19-4")
);
}
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
StoreSearchFilter는 제가 서비스로 생각하지 않았습니다. 검색 결과에 대한 필터링 조건을 담은 "도메인 로직"이라고 생각했습니다. 그래서 도메인으로 패키지를 옮길까하다가 하지 않았는데, 오히려 착각을 불러일으킨 것 같네요;;
그래서 따로 BaseServiceTest를 상속하지 않고 단위 테스트를 진행했습니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아~~!
StoreService에 Mapclilent가 실제 카카오 api 호출을 하니 그것만 Mock 처리하고
StoreService 자체는 실객체로 한다는거군요
저도 나눌수 있다면 최소한으로 Mock처리하고 테스트하는게 맞다고 생각해요 😄
StoreSearchFilter는 다시보니 도메인에 가깝긴 하네요 필터링을 하니까요
이제 이해했습니다!
해당 부분 다음 PR에 반영할께요
| @Builder | ||
| public Story( | ||
| Member member, | ||
| String storeKakaoId, | ||
| String storeName, | ||
| String storeAddress, | ||
| String storeCategory, | ||
| String description, | ||
| String imageKey | ||
| ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[제안] 빌더를 사용하고 생성자를 사용하지 않는 상황에서는 해당 접근 제어자를 private로 하는 것을 선호합니다. 이에 대해서 어떻게 생각하시나요?
📄 Terraform Plan Summary🛡️ Common InfrastructureStatus: ✅ No Changes 🛠️ Development EnvironmentStatus: ✅ No Changes 📋 Full Results: View in Actions |
|
📌 최신 ERD가 자동 생성되었습니다. 👉 ERD 보러가기 |
|
|
/noti 리뷰하시느라 고생많으셨습니다! 스토리 목록 조회 기능 개발로 인해 먼저 Merge하겠습니다. |
|
🎉 This PR is included in version 1.4.0-develop.20 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.5.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |



✨ 개요
🧾 관련 이슈
#65
🔍 참고 사항 (선택)
Summary by CodeRabbit
Summary by CodeRabbit
신규 기능
버그 수정
문서화
테스트
기타